﻿// -----------------------------------------------------------------------
// <copyright file="LockExpression.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentStatement
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Threading;

    /// <summary>
    /// 
    /// </summary>
    public class LockExpression : Expression
    {
        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MonitorEnter = typeof(Monitor).GetMethod("Enter", new[] { typeof(object), typeof(bool).MakeByRefType() });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MonitorExit = typeof(Monitor).GetMethod("Exit", new[] { typeof(object) });

        /// <summary>
        /// 
        /// </summary>
        protected readonly Expression Object2;

        /// <summary>
        /// 
        /// </summary>
        protected readonly Expression Body2;

        /// <summary>
        /// 
        /// </summary>
        protected readonly Expression Expression2;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="object"></param>
        /// <param name="body"></param>
        public LockExpression(Expression @object, Expression body)
        {
            if (@object == null)
            {
                throw new ArgumentNullException("@object");
            }

            if (body == null)
            {
                throw new ArgumentNullException("body");
            }

            this.Object2 = @object;
            this.Body2 = body;

            this.Expression2 = this.InternalReduce();
        }

        /// <summary>
        /// 
        /// </summary>
        public Expression Object
        {
            get
            {
                return this.Object2;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Expression Body
        {
            get
            {
                return this.Body2;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override ExpressionType NodeType
        {
            get
            {
                return ExpressionType.Extension;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool CanReduce
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override Type Type
        {
            get
            {
                return this.Body.Type;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override Expression Reduce()
        {
            return this.Expression2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
        protected Expression InternalReduce()
        {
            ParameterExpression lockTacken = Expression.Variable(typeof(bool));

            var block = new List<Expression>();

            ParameterExpression @object = Expression.Variable(this.Object.Type);

            block.Add(Expression.Assign(@object, this.Object));
            block.Add(Expression.Call(MonitorEnter, @object, lockTacken));
            block.Add(this.Body);

            Expression expression = Expression.Block(
                new[] { @object, lockTacken },
                new Expression[] 
                    { 
                        Expression.Assign(lockTacken, Expression.Constant(false)), 
                        Expression.TryFinally(
                            Expression.Block(block), 
                            Expression.IfThen(lockTacken, Expression.Call(MonitorExit, @object))) 
                    });

            return expression;
        }
    }
}